package com.joker.aigc.chat.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.joker.aigc.chat.dao.ContactDao;
import com.joker.aigc.chat.dao.RoomDao;
import com.joker.aigc.chat.domain.dto.GroupMemberAddDto;
import com.joker.aigc.chat.domain.dto.GroupMemberAdminAddDto;
import com.joker.aigc.chat.domain.dto.GroupMemberAdminRevokeDto;
import com.joker.aigc.chat.domain.dto.GroupMemberDelDto;
import com.joker.aigc.chat.domain.dto.GroupMemberExitDto;
import com.joker.aigc.chat.domain.dto.GroupMemberInvitationDto;
import com.joker.aigc.chat.domain.entity.Contact;
import com.joker.aigc.chat.domain.entity.GroupMember;
import com.joker.aigc.chat.domain.entity.Room;
import com.joker.aigc.chat.domain.entity.RoomFriend;
import com.joker.aigc.chat.domain.entity.RoomGroup;
import com.joker.aigc.chat.domain.enums.CommonErrorEnum;
import com.joker.aigc.chat.domain.enums.GroupErrorEnum;
import com.joker.aigc.chat.domain.enums.GroupRoleAPPEnum;
import com.joker.aigc.chat.domain.enums.GroupRoleEnum;
import com.joker.aigc.chat.domain.enums.HotFlagEnum;
import com.joker.aigc.chat.domain.enums.NormalOrNoEnum;
import com.joker.aigc.chat.domain.enums.RoomTypeEnum;
import com.joker.aigc.chat.service.GroupMemberService;
import com.joker.aigc.chat.service.MessageService;
import com.joker.aigc.chat.service.RoomFriendService;
import com.joker.aigc.chat.service.RoomGroupService;
import com.joker.aigc.chat.service.RoomService;
import com.joker.aigc.domain.entity.*;
import com.joker.aigc.domain.enums.*;
import com.joker.aigc.chat.domain.mapstruct.RoomGroupMapstruct;
import com.joker.aigc.chat.domain.vo.GroupMemberListVo;
import com.joker.aigc.chat.domain.vo.GroupMemberVo;
import com.joker.aigc.chat.domain.vo.RoomGroupVo;
import com.joker.aigc.dao.UserDao;
import com.joker.aigc.chat.mapper.ContactMapper;
import com.joker.aigc.chat.mapper.GroupMemberMapper;
import com.joker.aigc.chat.mapper.RoomGroupMapper;
import com.joker.aigc.chat.mapper.RoomMapper;
import com.joker.aigc.chat.service.adapter.ChatAdapter;
import com.joker.aigc.chat.service.event.GroupMemberAddEvent;
import com.joker.aigc.utils.AssertUtil;
import com.joker.aigc.service.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

import static com.joker.aigc.chat.domain.entity.GroupConst.MAX_MANAGE_COUNT;

/**
 * @author 20346
 * @description 针对表【room(房间表)】的数据库操作Service实现
 * @createDate 2023-12-17 11:26:08
 */
@Service
@Slf4j
@Transactional
@RequiredArgsConstructor
public class RoomServiceImpl extends ServiceImpl<RoomMapper, Room> implements RoomService {

    //
    final ContactMapper contactMapper;
    final RoomGroupMapper roomGroupMapper;
    final RoomGroupService roomGroupService;
    final RoomDao roomDao;
    final UserService userService;
    final GroupMemberMapper groupMemberMapper;
    final GroupMemberService groupMemberService;
    final UserRoleService userRoleService;
    final MessageService messageService;
    final RoomFriendService roomFriendService;
    final ApplicationEventPublisher applicationEventPublisher;
    final RoomGroupMapstruct roomGroupMapstruct;
    final ContactDao contactDao;
    final UserDao userDao;

    private boolean isHotGroup(Room room) {
        // 判断是否是热门用户
        return HotFlagEnum.YES.getId().equals(room.getHotFlag());
    }

    /**
     * 获取某一个群主 关于 在线人数，群消息
     *
     * @param uid    用户id
     * @param roomId 房间id
     * @return 返回房间的具体信息
     */
    @Override
    public RoomGroupVo groupDetail(Long uid, Long roomId) {
//        return null;
        // 首先获取 群组消息
        RoomGroup roomGroup = roomGroupMapper.selectOne(new LambdaQueryWrapper<RoomGroup>().eq(RoomGroup::getRoomId, roomId));
        AssertUtil.isNotEmpty(roomGroup, "房间id 不存在");
        RoomGroupVo groupVo = roomGroupMapstruct.toVO(roomGroup);
        // 查找房间号
        Room room = roomDao.getById(roomId);
        AssertUtil.isNotEmpty(roomGroup, "roomId有误");
        Integer onlineNum;
        if (isHotGroup(room)) {
            onlineNum = userService.getOnlineNum(); // 热点群从redis取人数
        } else {
            // 首先是获取 该 组织下所有的用户信息
            List<Long> memberUidList = groupMemberService.getMemberUidList(roomGroup.getId());
            onlineNum = userService.getOnlineCount(memberUidList);
        }
        GroupRoleAPPEnum groupRole = getGroupRole(uid, roomGroup, room);
        groupVo.setRole(groupRole.getType());
        groupVo.setOnlineNum(onlineNum);
        return groupVo;
    }

    @Override
    public List<Room> getHotRoom() {
        return lambdaQuery().eq(Room::getHotFlag, HotFlagEnum.YES.getId()).list();
    }

    @Override
    public List<RoomGroupVo> queryGroupList(Long uid) {
        List<Long> uids = groupMemberService.getRoomGroupIdSelfGroup(uid);
        if (CollectionUtils.isEmpty(uids)) {
            return null;
        }
        List<RoomGroup> groups = roomGroupService.listByIds(uids);
        return roomGroupMapstruct.toVO(groups);
    }

    private GroupRoleAPPEnum getGroupRole(Long uid, RoomGroup roomGroup, Room room) {
        GroupMember member = Objects.isNull(uid) ? null : groupMemberService.getMember(roomGroup.getId(), uid);
        if (Objects.nonNull(member)) {
            return GroupRoleAPPEnum.of(member.getRole());
        } else if (isHotGroup(room)) {
            return GroupRoleAPPEnum.MEMBER;
        } else {
            return GroupRoleAPPEnum.REMOVE;
        }
    }


    /**
     * @param roomId
     * @return
     */
    @Override
    public List<GroupMemberVo> getMemberPage(Long roomId) {
        Room room = roomDao.getById(roomId);
        AssertUtil.isNotEmpty(room, "对不起房间号不存在");
        // 查询群组 id
        RoomGroup roomGroup = roomGroupService.getRoomGroupByRoomId(roomId);
        // 获取该 群组消息下所有的 用户信息
//        List<Long> memberUidList = groupMemberService.getMemberUidList(roomGroup.getId());

        // 凭借sql
        MPJLambdaWrapper<GroupMember> wrapper = new MPJLambdaWrapper<>();
        wrapper.select(GroupMember::getRole, GroupMember::getId)
                .selectAs(User::getId, "uid")
                .select(User::getAvatar, User::getActiveStatus, User::getUsername, User::getLastOptTime)
                .leftJoin(User.class, User::getId, GroupMember::getUid)
                .eq(GroupMember::getGroupId, roomGroup.getId());
//                .in(User::getId, memberUidList);
        return groupMemberMapper.selectJoinList(GroupMemberVo.class, wrapper);
    }

    @Override
    public List<GroupMemberListVo> getMemberList(Long roomId) {
        // step 1:查找房间号
        Room room = roomDao.getById(roomId);
        AssertUtil.isNotEmpty(room, "对不起房间号不存在");
        // step 2: 查找出 roomGroup id
        RoomGroup roomGroup = roomGroupService.getRoomGroupByRoomId(roomId);

        //  step 3: 获取该 群组消息下所有的 用户的id
        // 凭借sql
        MPJLambdaWrapper<GroupMember> wrapper = new MPJLambdaWrapper<>();
        wrapper.selectAs(User::getId, "uid")
                .select(User::getAvatar, User::getUsername)
                .leftJoin(User.class, User::getId, GroupMember::getUid)
                .eq(GroupMember::getGroupId, roomGroup.getId());
        return groupMemberMapper.selectJoinList(GroupMemberListVo.class, wrapper);
    }

    /**
     * 删除成员
     *
     * @param uid
     * @param dto
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void delMember(Long uid, GroupMemberDelDto dto) {

        // 管理员 来删除用户成员
        Room room = roomDao.getById(dto.getRoomId());
        AssertUtil.isNotEmpty(room, "房间号不存在");

        // 查找到群组信息
        RoomGroup roomGroup = roomGroupService.getRoomGroupByRoomId(room.getId());

        // 首先查找到想要删除的人，
        GroupMember self = groupMemberService.getMember(roomGroup.getId(), uid);

        // 1. 判断被移除的人是否是群主或者管理员  （群主不可以被移除，管理员只能被群主移除）
        Long removedUid = dto.getUid();

        // 1.1 群主 非法操作. 想要移除的人是群主。
        AssertUtil.isFalse(groupMemberService.isLord(roomGroup.getId(), removedUid), GroupErrorEnum.NOT_ALLOWED_FOR_REMOVE.getMsg());

        // 1.2 管理员 判断是否是群主操作
        if (groupMemberService.isManager(roomGroup.getId(), removedUid)) {
            Boolean isLord = groupMemberService.isLord(roomGroup.getId(), uid);
            AssertUtil.isTrue(isLord, GroupErrorEnum.NOT_ALLOWED_FOR_REMOVE.getMsg());
        }

        // 1.3 普通成员 判断是否有权限操作
        AssertUtil.isTrue(hasPower(self), GroupErrorEnum.NOT_ALLOWED_FOR_REMOVE.getMsg());
        // 获取需要移除用户的信息
        GroupMember member = groupMemberService.getMember(roomGroup.getId(), removedUid);
        AssertUtil.isNotEmpty(member, "用户已经移除");
        // 确定了是该成员。直接移除
        groupMemberService.removeById(member.getId());

    }

    private boolean hasPower(GroupMember self) {
        return Objects.equals(self.getRole(), GroupRoleEnum.LEADER.getType())
                || Objects.equals(self.getRole(), GroupRoleEnum.MANAGER.getType())
                || userRoleService.hasPower(self.getUid(), RoleEnum.ADMIN);
    }

    /**
     * 退出群聊
     *
     * @param uid 用户id
     * @param dto 退出信息
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void exitGroup(Long uid, GroupMemberExitDto dto) {

        Long roomId = dto.getRoomId();
        // 首先判断群聊是否存在
        RoomGroup roomGroup = roomGroupService.getRoomGroupByRoomId(roomId);
        AssertUtil.isNotEmpty(roomGroup, GroupErrorEnum.GROUP_NOT_EXIST.getMsg());

        // 第二步 判断房间是否是大群聊。如果是大群聊的话禁止退出
        Room room = roomDao.getById(roomId);
        AssertUtil.isFalse(room.isHotRoom(), GroupErrorEnum.NOT_ALLOWED_FOR_EXIT_GROUP.getMsg());

        // 第三步 ，判断群成员 是否在群中
        Boolean isGroupShip = groupMemberService.isGroupShip(roomGroup.getRoomId(), Collections.singletonList(uid));
        AssertUtil.isTrue(isGroupShip, GroupErrorEnum.USER_NOT_IN_GROUP.getMsg());

        // 4. 判断该用户是否是群主
        Boolean isLord = groupMemberService.isLord(roomGroup.getId(), uid);

        if (isLord) {
            // 4.1 删除房间
            boolean isDelRoom = roomGroupService.removeById(roomId);
            AssertUtil.isTrue(isDelRoom, CommonErrorEnum.SYSTEM_ERROR.getMsg());
            // 4.2 删除会话
            Boolean isDelContact = contactDao.removeByRoomId(roomId, Collections.EMPTY_LIST);
            AssertUtil.isTrue(isDelContact, CommonErrorEnum.SYSTEM_ERROR.getMsg());
            // 4.3 删除群成员
            Boolean isDelGroupMember = groupMemberService.removeByGroupId(roomGroup.getId(), Collections.EMPTY_LIST);
            AssertUtil.isTrue(isDelGroupMember, CommonErrorEnum.SYSTEM_ERROR.getMsg());
            // 4.4 删除消息记录 (逻辑删除)
            Boolean isDelMessage = messageService.removeByRoomId(roomId, Collections.EMPTY_LIST);
            AssertUtil.isTrue(isDelMessage, CommonErrorEnum.SYSTEM_ERROR.getMsg());
            // TODO 这里也可以告知群成员 群聊已被删除的消息
        } else {
            // 4.5 删除会话
            Boolean isDelContact = contactDao.removeByRoomId(roomId, Collections.singletonList(uid));
            AssertUtil.isTrue(isDelContact, CommonErrorEnum.SYSTEM_ERROR.getMsg());
            // 4.6 删除群成员
            Boolean isDelGroupMember = groupMemberService.removeByGroupId(roomGroup.getId(), Collections.singletonList(uid));
            AssertUtil.isTrue(isDelGroupMember, CommonErrorEnum.SYSTEM_ERROR.getMsg());
//            // 4.7 发送移除事件告知群成员
//            List<Long> memberUidList = groupMemberCache.getMemberUidList(roomGroup.getRoomId());
//            WSBaseResp<WSMemberChange> ws = MemberAdapter.buildMemberRemoveWS(roomGroup.getRoomId(), uid);
//            pushService.sendPushMsg(ws, memberUidList);
//            groupMemberCache.evictMemberUidList(room.getId());
        }
    }

    /**
     * 创建群聊
     *
     * @param uid
     * @param groupMemberAddDto
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Long addGroup(Long uid, GroupMemberAddDto groupMemberAddDto) {

        Set<Long> uidList = new HashSet<>(groupMemberAddDto.getUidList());
        // 如果 前端传递错误，uidList包含在 邀请人当中。需要去除
        uidList.remove(uid);
        // step 1 拉群的好友是否都存在
        AssertUtil.isTrue(userDao.usersAllExist(uidList), "部分用户id不存在");
        // step 2  创建房间 Room  ,RoomGroup 这两个数据 进行创建
        RoomGroup roomGroup = createGroupRoom(uid);
        // step 3 批量保存群成员 GroupMember 。
        List<GroupMember> groupMembers = groupMemberService.buildAndSaveGroupMemberBatch(groupMemberAddDto.getUidList(), roomGroup.getId());
        // step 4 开始创建会话 Contact
        List<Contact> contacts = new ArrayList<>();

        // 加上 群主，群主也需要创建会话
        uidList.add(uid);
        //  最后一步 然后是添加会话
        uidList.forEach(item -> {
            Contact contact = new Contact();
            contact.setUid(item);
            contact.setRoomId(roomGroup.getRoomId());
            contact.setActiveTime(new Date());
            contact.setReadTime(new Date());
            contacts.add(contact);
        });
        // 批量保存 群组消息
        contactDao.saveBatch(contacts);
        // 发送邀请加群消息==》触发每个人的会话
        applicationEventPublisher.publishEvent(new GroupMemberAddEvent(this, roomGroup, groupMembers, uid));
        return roomGroup.getRoomId();
    }

    /**
     * 添加群成员
     *
     * @param uid     邀请人
     * @param request 邀请人员
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void addMember(Long uid, GroupMemberInvitationDto request) {
        // 添加群成员 第一步就是查找到 room 房间号。
        Room room = roomDao.getById(request.getRoomId());
        AssertUtil.isNotEmpty(room, "房间号有误"); // 校验房间号是否存在
        AssertUtil.isFalse(isHotGroup(room), "全员群无需邀请好友"); // 验证是否是 全员群。这样子的话。就不需要通过 添加成员了。默认就在其中
        RoomGroup roomGroup = roomGroupService.getRoomGroupByRoomId(request.getRoomId()); //
        AssertUtil.isNotEmpty(roomGroup, "房间号有误"); // 获得 群组消息
        // 查看是否是邀请人 是否是群成员 ,只有群成员才有资格 邀请别人
        GroupMember self = groupMemberService.getMember(roomGroup.getId(), uid);
        AssertUtil.isNotEmpty(self, "您不是群成员");// 如果不是群成员的话，则退出
        // 获取 该群的所有成员列表
        List<Long> memberBatch = groupMemberService.getMemberBatch(roomGroup.getId(), request.getUidList());
        // 变成set 去除重复数据
        Set<Long> existUid = new HashSet<>(memberBatch);
        // 去除已经在其中的数据
        List<Long> waitAddUidList = request.getUidList().stream().filter(a -> !existUid.contains(a)).distinct().collect(Collectors.toList());
        // 如果邀请的人已经都在 房间中，就不需要进行操作了
        if (CollectionUtils.isEmpty(waitAddUidList)) {
            return;
        }
        // 这边是 构建操作

        List<GroupMember> groupMembers = groupMemberService.buildAndSaveGroupMemberBatch(waitAddUidList, roomGroup.getId());
        log.info("添加群成员-->{}", groupMembers);

        List<Contact> contacts = new ArrayList<>();
        //  最后一步 然后是添加会话
        waitAddUidList.forEach(item -> {
            Contact contact = new Contact();
            contact.setUid(item);
            contact.setRoomId(room.getId());
            contact.setActiveTime(new Date());
            contact.setReadTime(new Date());
            contacts.add(contact);
        });
        contactDao.saveBatch(contacts);
        // 接下来就是发送 消息通知，让在线的人知道这个消息
        applicationEventPublisher.publishEvent(new GroupMemberAddEvent(this, roomGroup, groupMembers, uid));
    }

    /**
     * 添加管理员
     *
     * @param uid     用户的id信息
     * @param request 需要添加管理员的id
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void addAdmin(Long uid, GroupMemberAdminAddDto request) {

        // 1. 判断群聊是否存在
        RoomGroup roomGroup = roomGroupService.getRoomGroupByRoomId(request.getRoomId());
        AssertUtil.isNotEmpty(roomGroup, GroupErrorEnum.GROUP_NOT_EXIST.getMsg());

        // 2. 判断该用户是否是群主
        Boolean isLord = groupMemberService.isLord(roomGroup.getId(), uid);
        AssertUtil.isTrue(isLord, GroupErrorEnum.NOT_ALLOWED_OPERATION.getMsg());

        // 3. 判断群成员是否在群中
        Boolean isGroupShip = groupMemberService.isGroupShip(roomGroup.getRoomId(), request.getUidList());
        AssertUtil.isTrue(isGroupShip, GroupErrorEnum.USER_NOT_IN_GROUP.getMsg());

        // 4. 判断管理员数量是否达到上限
        // 4.1 查询现有管理员数量
        List<Long> manageUidList = groupMemberService.getManageUidList(roomGroup.getId());
        // 4.2 去重
        HashSet<Long> manageUidSet = new HashSet<>(manageUidList);
        manageUidSet.addAll(request.getUidList());
        AssertUtil.isFalse(manageUidSet.size() > MAX_MANAGE_COUNT, GroupErrorEnum.MANAGE_COUNT_EXCEED.getMsg());

        // 5. 增加管理员
        groupMemberService.addAdmin(roomGroup.getId(), request.getUidList());
    }

    /**
     * 撤销群管理员
     *
     * @param uid     用户id
     * @param request 撤销的消息
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void revokeAdmin(Long uid, GroupMemberAdminRevokeDto request) {
        // 1. 判断群聊是否存在
        RoomGroup roomGroup = roomGroupService.getRoomGroupByRoomId(request.getRoomId());
        AssertUtil.isNotEmpty(roomGroup, GroupErrorEnum.GROUP_NOT_EXIST.getMsg());

        // 2. 判断该用户是否是群主
        Boolean isLord = groupMemberService.isLord(roomGroup.getId(), uid);
        AssertUtil.isTrue(isLord, GroupErrorEnum.NOT_ALLOWED_OPERATION.getMsg());

        // 3. 判断群成员是否在群中
        Boolean isGroupShip = groupMemberService.isGroupShip(roomGroup.getRoomId(), request.getUidList());
        AssertUtil.isTrue(isGroupShip, GroupErrorEnum.USER_NOT_IN_GROUP.getMsg());

        // 4. 撤销管理员
        groupMemberService.revokeAdmin(roomGroup.getId(), request.getUidList());
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public RoomFriend createFriendRoom(List<Long> uidList) {
        AssertUtil.isNotEmpty(uidList, "房间创建失败，好友数量不对");

        AssertUtil.equal(uidList.size(), 2, "房间创建失败，好友数量不对");
        // 生成roomkey
        String key = ChatAdapter.generateRoomKey(uidList);
        // 生成
        RoomFriend roomFriend = roomFriendService.getByKey(key);
        if (Objects.nonNull(roomFriend)) { //如果存在房间就恢复，适用于恢复好友场景
            restoreRoomIfNeed(roomFriend);
        } else {
            //新建房间
            Room room = createRoom(RoomTypeEnum.FRIEND);
            roomFriend = createFriendRoom(room.getId(), uidList);
        }
        return roomFriend;
    }

    /**
     * 根据 双方的 id 判断两个人是否是好友
     *
     * @param uid1 id1
     * @param uid2 id2
     * @return
     */
    @Override
    public RoomFriend getFriendRoom(Long uid1, Long uid2) {
        String key = ChatAdapter.generateRoomKey(Arrays.asList(uid1, uid2));
        return roomFriendService.getByKey(key);
    }

    @Override
    public void disableFriendRoom(List<Long> uidList) {
        AssertUtil.isNotEmpty(uidList, "房间创建失败，好友数量不对");
        AssertUtil.equal(uidList.size(), 2, "房间创建失败，好友数量不对");
        String key = ChatAdapter.generateRoomKey(uidList);
        roomFriendService.disableRoom(key);
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public RoomGroup createGroupRoom(Long uid) {

        User user = userService.getById(uid);
        // 创建房间号
        Room room = createRoom(RoomTypeEnum.GROUP);
        //创建 room_group
        RoomGroup roomGroup = ChatAdapter.buildGroupRoom(user, room.getId());
        roomGroupService.save(roomGroup);
        //插入群主
        GroupMember leader = GroupMember.builder()
                .role(GroupRoleEnum.LEADER.getType())
                .groupId(roomGroup.getId())
                .uid(uid)
                .build();
        groupMemberService.save(leader);
        return roomGroup;
    }

    /**
     * 创建单聊信息
     *
     * @param roomId
     * @param uidList
     * @return
     */
    private RoomFriend createFriendRoom(Long roomId, List<Long> uidList) {
        RoomFriend insert = ChatAdapter.buildFriendRoom(roomId, uidList);
        roomFriendService.save(insert);
        return insert;
    }

    private Room createRoom(RoomTypeEnum typeEnum) {
        Room insert = ChatAdapter.buildRoom(typeEnum);
        save(insert);
        return insert;
    }

    private void restoreRoomIfNeed(RoomFriend room) {
        if (Objects.equals(room.getStatus(), NormalOrNoEnum.NOT_NORMAL.getStatus())) {
            roomFriendService.restoreRoom(room.getId());
        }
    }
}




